﻿#if NETSTANDARD2_0 || NETCOREAPP3_0
using Microsoft.Extensions.Logging;
#endif
using Longbow.Logging;
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;

namespace Longbow.Net
{
    /// <summary>
    /// ISocketServer 内部实现类
    /// </summary>
    internal sealed class InternalSocketServer : ISocketServer
    {
        private TcpListener _listener;
        private readonly ILogger _logger;
        private FileLoggerProvider _loggerProvider;
        private readonly IPEndPoint _localEP;

        /// <summary>
        /// 获得 SocketServerOptions 配置
        /// </summary>
        /// <value>The options.</value>
        public SocketServerOptions Options { get; }

        /// <summary>
        /// 获得 SocketServerStatus 状态
        /// </summary>
        public SocketServerStatus Status { get; private set; }

        /// <summary>
        /// 获得 上一次操作异常实例
        /// </summary>
        public Exception Exception { get; private set; }

        /// <summary>
        /// 获得 当前已建立连接的客户端集合
        /// </summary>
        public ConcurrentDictionary<string, ISocketClient> Clients { get; } = new ConcurrentDictionary<string, ISocketClient>();

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="host">主机 IP 地址</param>
        /// <param name="port">主机 Port 端口</param>
        /// <param name="configure">SocketServerOptions 配置回调函数</param>
        public InternalSocketServer(string host, int port, Action<SocketServerOptions> configure = null)
        {
            if (port < 1) throw new InvalidOperationException(nameof(port));

            Options = new SocketServerOptions();
            configure?.Invoke(Options);
            _localEP = new IPEndPoint(string.IsNullOrEmpty(host) ? IPAddress.Any : IPAddress.Parse(host), port);

            var logFile = Path.Combine(Path.GetDirectoryName(Options.LogFileName), $"{Path.GetFileNameWithoutExtension(Options.LogFileName)}-{_localEP.Address}-{_localEP.Port}{Path.GetExtension(Options.LogFileName)}");
            _loggerProvider = new FileLoggerProvider(new FileLoggerOptions() { FileName = logFile });
            _logger = _loggerProvider.CreateLogger<InternalSocketServer>();
        }

        /// <summary>
        /// Socket Server 开始侦听方法
        /// </summary>
        /// <param name="acceptCallback">接收客户端回调方法</param>
        /// <param name="receiveCallback">接收数据回调方法</param>
        /// <param name="configure">连接客户端时配置项信息</param>
        /// <returns>成功时返回真</returns>
        public bool Start(Action<ISocketClient> acceptCallback, Action<ISocketClient, byte[]> receiveCallback, Action<SocketConnectOptions> configure = null)
        {
            if (acceptCallback == null) throw new ArgumentNullException(nameof(acceptCallback));
            if (receiveCallback == null) throw new ArgumentNullException(nameof(receiveCallback));
            if (_listener != null) throw new InvalidOperationException("SocketServer already starting!");

            var ret = false;
            try
            {
                _listener = new TcpListener(_localEP);
                Log(LogLevel.Information, "Start");
                _listener.Start();
                AcceptAsync(acceptCallback, receiveCallback, configure);
                ret = true;
            }
            catch (Exception ex)
            {
                Status = SocketServerStatus.SocketError;
                Exception = ex;
                Log(LogLevel.Error, $"Start Failed{Environment.NewLine}Message: {ex.Message}");
            }
            return ret;
        }

        private void AcceptAsync(Action<ISocketClient> acceptCallback, Action<ISocketClient, byte[]> receiveCallback, Action<SocketConnectOptions> configure = null)
        {
            Log(LogLevel.Information, "Begin Accept Client");
            Task.Run(async () =>
            {
                while (_listener != null)
                {
                    var tcpClient = await _listener.AcceptTcpClientAsync();
                    var client = SocketFactory.CreateClient(tcpClient, op =>
                    {
                        op.LogFileName = Options.LogFileName;
                        op.Enabled = Options.Enabled;
                        configure?.Invoke(op);
                    });
                    Log(LogLevel.Information, $"Accept Client {client.RemoteEndPoint}");
                    Clients.TryAdd(client.RemoteEndPoint.ToString(), client);
                    acceptCallback.Invoke(client);
                    client.ReadAsync((socket, data) =>
                    {
                        if (data.Length == 0)
                        {
                            Log(LogLevel.Information, $"Disconnected By Remote Client {socket.RemoteEndPoint}");
                            Clients.TryRemove(socket.RemoteEndPoint.ToString(), out var s);
                        }
                        receiveCallback.Invoke(socket, data);
                    });
                }
            });
        }

        private void Log(LogLevel logLevel, string msg)
        {
            if (Options.Enabled)
            {
                _logger.Log(logLevel, $"{DateTime.Now} -- {msg}");
            }
        }

        /// <summary>
        /// Socket Server 停止侦听方法
        /// </summary>
        public void Stop()
        {
            Clients.ToList().ForEach(item => item.Value.Dispose());
            Clients.Clear();
            _listener?.Stop();
            _listener = null;
            Log(LogLevel.Information, "Stopped");
        }

        private bool disposedValue; // To detect redundant calls
        private void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                disposedValue = true;

                if (disposing)
                {
                    Stop();
                    _loggerProvider.Dispose();
                    _loggerProvider = null;
                }
            }
        }

        /// <summary>
        /// Dispose 方法 内部调用 Stop 方法
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    }
}
